﻿using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

using SobrietyEngine.Actor;
using SobrietyEngine.Actor.Builders.UDMF;
using SobrietyEngine.Components;
using SobrietyEngine.Components.Common;
using SobrietyEngine.Components.Physics;
using SobrietyEngine.Components.Graphic;
using SobrietyEngine.Guids;
using SobrietyEngine.Logging;
using SobrietyEngine.Scene;
using SobrietyEngine.Physics;
using SobrietyEngine.UDMF;

namespace SobrietyEngine.Components.UDMF
{
    /// <summary>
    /// Spawns a UDMF map
    /// </summary>
    public class UDMFMapCmp:BaseComponent 
    {
        String mapAssetName;
        private UDMFMap map;

        private LinkedList<IActor> childActors = new LinkedList<IActor>();

        private IPosition posCmp;
        private IOrientation orientCmp;
        private IScale scaleCmp;

        public UDMFMapCmp()
            : base()
        {
        }

        public UDMFMapCmp(String mapName)
            : this()
        {           
            mapAssetName = mapName;
        }

        public override bool InitComponent()
        {

            posCmp = Actor.Get<IPosition>();
            orientCmp = Actor.Get<IOrientation>();
            scaleCmp = Actor.Get<IScale>();
            
            //spawn our map here.

            //map properties
            Vector3 mapPos = posCmp != null ? posCmp.PositionV3 : Vector3.Zero;
            Quaternion mapOrient = orientCmp != null ? orientCmp.Orientation : Quaternion.Identity;
            Vector3 mapUp = orientCmp != null ? orientCmp.Up : Vector3.Up;
            Vector3 mapScale = scaleCmp != null ? scaleCmp.Scale : Vector3.One;

            //Load

            String UDMFdata = Actor.GameServices().AssetGameService.Load<String>(mapAssetName);
            map = UDMFStringReader.Load(UDMFdata);
            
            //we are goign to compile our data, whch is essentially creating a reverse link from 
            //sectors to linedefs and sectors to verts, since UDMF links the other way
            //which makes it hard to make polygons from sectors information
            //we will also split polygons, triangulate sectors, etc. 
            map.Compile();
            
            //set some options
            map.Scale = mapScale;

            //create a matrix to represent our scaleCmp rotate translate, which we will multiple all
            //vertexes created by
            Matrix srt = Matrix.CreateScale(mapScale) * 
                Matrix.CreateFromQuaternion(mapOrient) *
                Matrix.CreateTranslation(mapPos);

            //spawn some stuff
            SpawnMap(srt);

            RequiresUpdate = false;
            return base.InitComponent();
        }

        public void CreateWallQuad(Linedef ld, float z1, float z2, Matrix srt)
        {
            //get verts
            Vector3[] verts = new Vector3[] { 
                    new Vector3(map.Verts[ld.VertPairIndex[0]].Position, z1),
                    new Vector3(map.Verts[ld.VertPairIndex[0]].Position, z2),
                    new Vector3(map.Verts[ld.VertPairIndex[1]].Position, z1),
                    new Vector3(map.Verts[ld.VertPairIndex[1]].Position, z2)
            };
            
            //transform verts
            verts[0] = Vector3.Transform(verts[0], srt);
            verts[1] = Vector3.Transform(verts[1], srt);
            verts[2] = Vector3.Transform(verts[2], srt);
            verts[3] = Vector3.Transform(verts[3], srt);

            //use sidedef to get texture info
            //select our sidedef based on zDiff if there are two sidefs
            //or non -1 value sidedef one if theres one sidedef
            float zDiff = z1 - z2;
            Sidedef sd = null;
            if(ld.SidedefFrontIndex != -1 && ld.SidedefBackIndex != -1)
                sd = zDiff > 0 ? map.Sidedefs[ld.SidedefFrontIndex] : map.Sidedefs[ld.SidedefBackIndex];
            else
                sd = ld.SidedefFrontIndex != -1 ? map.Sidedefs[ld.SidedefFrontIndex] : map.Sidedefs[ld.SidedefBackIndex];

            //determin our texture
            String tex = String.Empty;
            if (sd.Data.ContainsKey("texturemiddle"))
                tex = sd.Data["texturemiddle"];
            if (tex == String.Empty)
                tex = "RedGrid";//default for missing texture

            //create the wall quad if we are above a tolerance
            if (Math.Abs(zDiff) > 0.001f)
                childActors.AddLast(UDMFWallQuadBldr.Build(Actor.Scene, Actor.Guid, verts, "textures/" + tex));
        }


        public void SpawnMap(Matrix srt)
        {

            //make some walls based on UDMF map
            //spawn sector polygons now
            foreach (Sector sec in map.Sectors)
            {
                IActor secc = UDMFSectorBldr.Build(Actor.Scene, sec.ContourVerts[0], sec.CeilingHeight, sec.CeilingTexture, srt, false);
                TexturedPolygonCmp tpc = secc.Get<TexturedPolygonCmp>();
                for (int i = 1; i < sec.ContourVerts.Count && tpc != null; ++i)
                {
                    Vector3[] poly = new Vector3[sec.ContourVerts[i].Count];
                    for (int v = 0; v < sec.ContourVerts[i].Count; ++v)
                    {
                        poly[v] = new Vector3(sec.ContourVerts[i][v].Position, sec.CeilingHeight);
                    }
                    tpc.AddHole(poly, srt, false);
                }

                IActor secf = UDMFSectorBldr.Build(Actor.Scene, sec.ContourVerts[0], sec.FloorHeight, sec.FloorTexture, srt, true);
                tpc = secf.Get<TexturedPolygonCmp>();
                for (int i = 1; i < sec.ContourVerts.Count && tpc != null; ++i)
                {
                    Vector3[] poly = new Vector3[sec.ContourVerts[i].Count];
                    for (int v = 0; v < sec.ContourVerts[i].Count; ++v)
                    {
                        poly[v] = new Vector3(sec.ContourVerts[i][v].Position, sec.FloorHeight);
                    }
                    tpc.AddHole(poly, srt, true);
                }
            }

            //static geometry is based on linedefs
            foreach (Linedef ld in map.Linedefs)
            {
                //Create quads
                if (ld.TwoSided)
                {
                    Sidedef frontsd = map.Sidedefs[ld.SidedefFrontIndex];
                    Sidedef backsd = map.Sidedefs[ld.SidedefBackIndex];

                    Sector frontSec = map.Sectors[frontsd.SectorIndex];
                    Sector backSec = map.Sectors[backsd.SectorIndex];

                    //CreateWallQuad(ld, frontSec.CeilingHeight, backSec.CeilingHeight, srt);
                    CreateWallQuad(ld, backSec.CeilingHeight, frontSec.CeilingHeight, srt);
                    CreateWallQuad(ld, frontSec.FloorHeight, backSec.FloorHeight, srt);
                }
                else
                {
                    Sidedef sd = ld.SidedefFrontIndex != -1 ? map.Sidedefs[ld.SidedefFrontIndex] : map.Sidedefs[ld.SidedefBackIndex];
                    Sector sec = map.Sectors[sd.SectorIndex];

                    CreateWallQuad(ld, sec.FloorHeight, sec.CeilingHeight, srt);
                }

                //create farseer wall
                if (ld.Blocking == true)
                {
                    Vector3[] verts = { new Vector3(map.Verts[ld.VertPairIndex[0]].Position, 0),
                                        new Vector3(map.Verts[ld.VertPairIndex[1]].Position, 0) };
                    verts[0] = Vector3.Transform(verts[0], srt);
                    verts[1] = Vector3.Transform(verts[1], srt);

                    childActors.AddLast(UDMFWallBldr.Build(Actor.Scene, verts[0], verts[1]));
                }
            }



            //see if we can spawn some 'Things'. This will be handled transparently at some point


            //lets try spawning actors for our things
            //not sure how to handle component settings. perhaps theseneed to made in the defenitions
            //not everything _always_ needs to be costumizable.
            foreach (Thing t in map.Things)
            {
                //each thing has, at minimum x and y values.
                //need to add, as custom values
                //'actortype' to know what to spawn
                //'name' if we want it to have a name
                String type = t.Data["actortype"].Replace("\"", "");

                String name = String.Empty;
                if (t.Data.ContainsKey("instancename"))
                    name = t.Data["instancename"].Replace("\"", "").ToLower();

                //create the actor
                IActor thingActor = Actor.GameServices().ActorGameService.CreateActor(type, Actor.Scene);
                thingActor.Initialize();

                //set the position. UDMF labels then as X and Y for all things
                Vector3 thingPos = Vector3.Zero;
                thingPos.X = float.Parse(t.Data["x"]);
                thingPos.Y = float.Parse(t.Data["y"]);
                thingPos = Vector3.Transform(thingPos, srt);

                IPosition pos = thingActor.Get<IPosition>();
                pos.PositionV3 = thingPos;

                //if we have a name, lets try to name this thing:
                if (name != String.Empty)
                    thingActor.Guid.NameGuid(name);

            }
        }
    }
}
